#include "gwbase/db/sqlite3_wrapper.h"

#include <gwbase/base/logging.h>

namespace dicom {
namespace db {

static void LogSqlError(Database* db, const char* func,
                        const std::string& sql) {
  LOG_FATAL << "SQL_ERROR: " << func << ": " << sql << ": " << db->err_msg();
}

#define CHECK_OK(stmt, expect) \
  if (stmt != expect) LogSqlError(owner_, #stmt, sql_)

const int kFirstBindPos = 1;

Stmt::Stmt(Database* owner, const std::string& sql, sqlite3_stmt* stmt)
    : owner_(owner),
      sql_(sql),
      stmt_(stmt),
      bind_pos_(kFirstBindPos),
      eof_(false) {
  LOG_TRACE << "create statement: " << stmt_ << ": " << sql_;
}

Stmt::~Stmt() {
  CHECK_OK(::sqlite3_finalize(stmt_), SQLITE_OK);
  LOG_TRACE << "destroy statement: " << stmt_;
  LOG_TRACE << "stmt done: " << owner_;
  owner_->StmtDone();
}

void Stmt::BindInt32(int32_t v) {
  CHECK_OK(::sqlite3_bind_int(stmt_, bind_pos_++, v), SQLITE_OK);
}

void Stmt::BindInt64(int64_t v) {
  CHECK_OK(::sqlite3_bind_int64(stmt_, bind_pos_++, v), SQLITE_OK);
}

void Stmt::BindText(const char* s) {
  CHECK_OK(::sqlite3_bind_text(stmt_, bind_pos_++, s,
                               static_cast<int>(::strlen(s)), NULL),
           SQLITE_OK);
}

void Stmt::BindText(const std::string& s) {
  CHECK_OK(::sqlite3_bind_text(stmt_, bind_pos_++, s.c_str(),
                               static_cast<int>(s.size()), NULL),
           SQLITE_OK);
}

int32_t Stmt::GetInt32(int col) { return ::sqlite3_column_int(stmt_, col); }

int64_t Stmt::GetInt64(int col) { return ::sqlite3_column_int64(stmt_, col); }

const char* Stmt::GetText(int col) {
  return reinterpret_cast<const char*>(::sqlite3_column_text(stmt_, col));
}

void Stmt::Step() {
  assert(eof_ == false);
  int ret = ::sqlite3_step(stmt_);
  if (ret == SQLITE_DONE) {
    eof_ = true;
  } else if (ret == SQLITE_ROW) {
    // nothing
  } else {
    LogSqlError(owner_, "sqlite3_step", sql_);
  }
}

Status Database::Open(const std::string& path, DbPtr* db) {
  sqlite3* d = NULL;
  int ret = ::sqlite3_open(path.c_str(), &d);
  if (ret != SQLITE_OK) {
    LOG_ERROR << "failed to open database";
    return Status(Status::kOther, "failed to open database");
  }

  db->reset(new Database(d));
  return Status();
}

Database::~Database() {
  LOG_TRACE << "close databse: " << this;
  int ret = ::sqlite3_close(db_);
  assert(ret == SQLITE_OK);
  (void)ret;
}

StmtPtr Database::Prepare(const std::string& sql) {
  LOG_TRACE << this << " prepare statement: " << sql;
  // you must close last statement before executing next statement
  assert(outgong_stmt_ == false);

  ::sqlite3_stmt* stmt = NULL;
  const char* tail = NULL;
  int ret = ::sqlite3_prepare(db_, sql.c_str(), static_cast<int>(sql.size()),
                              &stmt, &tail);
  if (ret != SQLITE_OK)
    LOG_FATAL << "invalid sql: " << sql << ": " << err_msg();

  outgong_stmt_ = true;
  return StmtPtr(new Stmt(this, sql, stmt));
}

void Database::RunSql(const std::string& sql) {
  StmtPtr s = Prepare(sql);
  s->Step();
}

bool Database::TableExsits(const std::string& table_name) {
  StmtPtr s = Prepare(StringPrintf(
      "select count(*) from sqlite_master "
      "where type='table' and name='%s'",
      table_name.c_str()));
  s->Step();

  int n = s->GetInt32(0);
  return (n != 0);
}

const char* Database::err_msg() { return sqlite3_errmsg(db_); }
}
}
